package com.powernode.config;

import com.alibaba.fastjson.JSON;
import com.powernode.constant.AuthConstants;
import com.powernode.constant.BusinessEnum;
import com.powernode.constant.HttpConstants;
import com.powernode.model.LoginResult;
import com.powernode.model.Result;
import com.powernode.service.impl.UserDetailsServiceImpl;
import com.powernode.util.JSONUtils;
import com.powernode.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.UUID;

/**
 * security 安全框架 配置类
 * 定制化认证登录 配置方案
 * WebSecurityConfigurerAdapter : 安全配置适配器类
 */
@Configuration
@Slf4j
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private UserDetailsServiceImpl userDetailsService;

    /**
     * 重新定制 认证机制
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    /**
     * 网络请求配置
     * csrf : 跨站请求伪造 -- Cross-site request forgery , 挟制用户在当前已登录的WEB应用程序上执行非本意的操作的攻击方法  ,  通过向服务器发送伪造请求，进行恶意行为的攻击手段
     * CORS：是一种访问机制 -- Cross-Origin Resource Sharing
     * 同源策略 :  协议相同、Host(ip)相同、端口相同的条件，
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭跨域请求
        http.cors().disable();
        // 关闭 跨站请求伪造
        http.csrf().disable();
        // 关闭session使用策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 配置登录信息
        http.formLogin()
                .loginProcessingUrl(AuthConstants.LOGIN_URL)   // 指定请求登录的路径
                .successHandler(authenticationSuccessHandler())   // 成功登录处理器
                .failureHandler(authenticationFailureHandler());  // 登录失败处理器
        // 配置登出信息
        http.logout()
                .logoutUrl(AuthConstants.LOGIN_OUT)   // 指定登出的请求路径
                .logoutSuccessHandler(logoutSuccessHandler());  // 登出成功处理器

        // 配置其他请求必须进行身份的认证  -- 所有接口都必须认证通过后才可以访问
        http.authorizeHttpRequests().anyRequest().authenticated();
    }

    /**
     * 登录成功处理器
     * 生成 一个token
     * 将token 作为key 存入redis中
     * 封装 loginResult返回 token
     *
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (requset, response, authentication) -> {
            // 设置响应参数
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.UTF_8);
            // 生成token  -- 使用uuid
            String token = UUID.randomUUID().toString();
            // 将token最为key redis中 存入对应的 用户信息 , 之后可以通过token 获取用户信息
            // 获取用户信息 , 从authentication中获取数据
            // 并将用户对象 转换为 json对象
            log.info("存入token的用户信息如下 : {}", authentication.getPrincipal().toString());
            String userStr = JSON.toJSONString(authentication.getPrincipal());
            stringRedisTemplate.opsForValue().set(AuthConstants.LOGIN_TOKEN_PREFIX + token, userStr, Duration.ofSeconds(AuthConstants.TOKEN_TIME));
            // 封装响应对象 ,返回
            LoginResult loginResult = LoginResult.builder()
                    .accessToken(token)
                    .expireIn(AuthConstants.TOKEN_TIME)
                    .build();
            // 将封装的对象转json , 并响应
            Result<LoginResult> result = Result.success(loginResult);
            ResponseUtils.writer(response, JSONUtils.objToJson(result));
        };
    }

    /**
     * 登录失败处理器  --
     *  设置响应信息
     *  封装返回结果
     *  返回结果对象
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request , response , exception) -> {
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.UTF_8);
            // 封装返回结果
            Result<String> result = new Result<>();
            result.setCode(BusinessEnum.UN_AUTHORIZATION.getCode()); // 未授权code : 401
            if (exception instanceof BadCredentialsException) {
                // it means the account is neither locked nor disabled. :
                result.setMsg("帐户未被锁定或禁用");
            } else if (exception instanceof UsernameNotFoundException) {
                // a UserDetailsService implementation cannot locate a User by its username.
                result.setMsg("无法通过用户名找到用户");
            } else if (exception instanceof AccountExpiredException) {
                // the account has expired
                result.setMsg("认证过期");
            } else if (exception instanceof InternalAuthenticationServiceException) {
                // an authentication request could not be processed due to a system problem that occurred internally.
                result.setMsg(exception.getMessage());
            } else {
                result.setMsg(BusinessEnum.OPERATION_FAIL.getMsg());  // 操作失败
            }
            // 返回结果对象
            ResponseUtils.writer(response, JSONUtils.objToJson(result));
        };
    }

    /**
     * 登出成功处理器  :  解决用户退出后token不过期问题
     * 获取请求头中的token
     * 根据请求头中的token  去删除 redis中的token
     * @return
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (request , response , authentication) -> {
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.UTF_8);
            // 从请求头中和获取token
            String authorization = request.getHeader(AuthConstants.AUTHORIZATION);
            String token = authorization.replaceFirst(AuthConstants.BEARER, "");
            // 从redis中删除对应的tokenkey
            stringRedisTemplate.delete(AuthConstants.LOGIN_TOKEN_PREFIX + token);
            // 返回结果对象
            Result<Object> result = Result.success(null);
            ResponseUtils.writer(response, JSONUtils.objToJson(result));
        };
    }

}
